package com.wr.executer;

import com.wr.pojo.Configuration;
import com.wr.pojo.MappedStatement;
import com.wr.utils.GenericTokenParser;
import com.wr.utils.ParameterMapping;
import com.wr.utils.ParameterMappingTokenHandler;
import com.wr.utils.TokenHandler;

import javax.sql.DataSource;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 王瑞
 * @Description : 具体jdbc的实现类
 * @date 2020/1/29 - 21:17
 */
public class SimpleExecuter implements Executer {
    //真正的jdbc基本就在这里实现
    /*这里就是在写jdbc真正来连接数据库操作数据
    * 这里主要用到反射和内省来完成sql参数的赋值和返回结果的封装工作 故核心是这一部分
    * 在对sql的赋值时 从mappedStatement对象中可以获得参数的类路径 利用这个数据来反射
    * 在对结果集封装时  用java beans提供的内省库中的PropertyDescripter类来封装返回的实体  对实体的具体属性进行写操作
    * */

    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... args) throws Exception {
        //1.获取数据源
        DataSource dataSource = configuration.getDataSource();
        //2.获得数据库连接
        Connection connection = dataSource.getConnection();
        //3.获得sql
        //需要解析sql语句 对占位符赋值
        String sql = mappedStatement.getSql();
        //4.获得转换折后的sql处理对象boundsql
        BoundSql boundSql = getBoundSql(sql);
        //5.获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //6.对预处理对象设置参数
           //获得参数类型的路径
        String parameterType = mappedStatement.getParameterType();
        //获得参数的类对象
        Class<?> classType = getClassType(parameterType);
        //获得参数
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            //获得参数名称
            String content = parameterMapping.getContent();
            //使用反射来根据属性的名称获取参数的属性对象declaredField
            Field declaredField = classType.getDeclaredField(content);
            declaredField.setAccessible(true);
            //利用属性对象declaredField和具体的参数args来获取属性传递的参数锁对应得属性的具体属性值
            Object o = declaredField.get(args[0]);
           //7.在这里为预编译对象赋值
            //拿到具体的属性值o  然后赋值给预编译对象
            preparedStatement.setObject(i+1,o);
        }

        //7.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        //获取返回值类型的全路径
        String resultType = mappedStatement.getResultType();
        //获取返回值得类对象
        Class<?> resultClassType = getClassType(resultType);


        List<Object> list = new ArrayList<Object>();
        while (resultSet.next()){
            //反射创建一个返回值实体  这个o实体 就是需要被利用内省来封装属性并返回的实体
            Object o = resultClassType.newInstance();
            //获取结果集的每一行元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            //遍历每一行的每一列数据
            for (int i = 1; i <=metaData.getColumnCount() ; i++) {
                //获取列名
                String columnName = metaData.getColumnName(i);
                //获取该行的具体数据
                Object value = resultSet.getObject(columnName);
                //***重要：使用内省 ，根据数据库表的列名与实体类的属性名对应关系来完成返回值封装
                   //PropertyDescriptor是java beans内省库提供的类   构造器中的参数一个是列名，也就是需要封装的实体的属性名 一个是类的类对象
                   //在实例化PropertyDescriptor类的过程中，会对resultClassType对应的类 ，来生成columnName对应得属性的读写方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultClassType);
                //获取resultClassType对应得类对columnName属性的写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //把列数据value写到实体对象o中 完成参数的封装  到此 返回结果集的数据封装完成 o已经有属性 并且属性也被赋值
                writeMethod.invoke(o,value);
            }
            //将封装好的结果对象o放到list集合
            list.add(o);
        }
        //8.封装返回结果集
        return (List<E>) list;
    }

    /*
    * 完成对sql语句的解析 将#{} 用？进行代替   并解析{}里面的值进行存贮
    * */
    public BoundSql getBoundSql(String sql){

        //mybatis提供的标记处理类  来解析对占位符的解析
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        //标记解析器
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",tokenHandler);
        //解析出来的sql
        String parse = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
        //封装boundsql对象
        BoundSql boundSql = new BoundSql(parse,parameterMappings);

        return boundSql;
    }

    // 这个方法 用来根据一个类的全路径来获得这个类的类对象  核心方法就是静态方法Class.forName来注册一个类对象
    public Class<?> getClassType(String parameterType) throws ClassNotFoundException{
        if (parameterType!=null){
            Class<?> aClass = Class.forName(parameterType);
            return aClass;
        }else {
            return null;
        }
    }
}

